﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace Goortom.Xna {
    public class ContainerGameComponent : DrawableGameComponent {

        readonly GameComponentCollection _components;
        readonly List<IDrawable> _drawableComponents;
        readonly List<IUpdateable> _updateableComponents;
        readonly List<IGameComponent> _notYetInitialized;
        bool _isInitialized;

        public GameComponentCollection Components { get { return _components; } }

        public ContainerGameComponent(Game game) : base(game)
        {
            _components = new GameComponentCollection();
            _components.ComponentAdded += GameComponentAdded;
            _components.ComponentRemoved += GameComponentRemoved;

            _drawableComponents = new List<IDrawable>();
            _updateableComponents = new List<IUpdateable>();
            _notYetInitialized = new List<IGameComponent>();
        }

        #region DrawableGameComponent

        public override void Initialize() {
            while (_notYetInitialized.Count != 0) {
                _notYetInitialized[0].Initialize();
                _notYetInitialized.RemoveAt(0);
            }
            _isInitialized = true;
            base.Initialize();
        }

        public override void Draw(GameTime gameTime)
        {
            if (!Visible) return;
            for (int i = 0; i < _drawableComponents.Count; i++) {
                IDrawable drawable = _drawableComponents[i];
                if (drawable.Visible) {
                    drawable.Draw(gameTime);
                }
            }
        }

        public override void Update(GameTime gameTime) {
            if (!Enabled) return;
            for (int i = 0; i < _updateableComponents.Count; i++) {
                IUpdateable updateable = _updateableComponents[i];
                if (updateable.Enabled) {
                    updateable.Update(gameTime);
                }
            }
        }

        #endregion

        #region Components manage

        private void GameComponentRemoved(object sender, GameComponentCollectionEventArgs e) {
            if (!_isInitialized) {
                _notYetInitialized.Remove(e.GameComponent);
            }
            IUpdateable updateable = e.GameComponent as IUpdateable;
            if (updateable != null) {
                _updateableComponents.Remove(updateable);
                updateable.UpdateOrderChanged -= UpdateableUpdateOrderChanged;
            }
            IDrawable drawable = e.GameComponent as IDrawable;
            if (drawable != null) {
                _drawableComponents.Remove(drawable);
                drawable.DrawOrderChanged -= DrawableDrawOrderChanged;
            }
        }
        private void GameComponentAdded(object sender, GameComponentCollectionEventArgs e) {
            if (_isInitialized) {
                e.GameComponent.Initialize();
            } else {
                _notYetInitialized.Add(e.GameComponent);
            }
            IUpdateable updateable = e.GameComponent as IUpdateable;
            if (updateable != null) {
                int num = _updateableComponents.BinarySearch(updateable, UpdateOrderComparer.Default);
                if (num < 0) {
                    num = ~num;
                    while (num < _updateableComponents.Count && _updateableComponents[num].UpdateOrder == updateable.UpdateOrder) {
                        num++;
                    }
                    _updateableComponents.Insert(num, updateable);
                    updateable.UpdateOrderChanged += UpdateableUpdateOrderChanged;
                }
            }
            IDrawable drawable = e.GameComponent as IDrawable;
            if (drawable != null) {
                int num2 = _drawableComponents.BinarySearch(drawable, DrawOrderComparer.Default);
                if (num2 < 0) {
                    num2 = ~num2;
                    while (num2 < _drawableComponents.Count && _drawableComponents[num2].DrawOrder == drawable.DrawOrder) {
                        num2++;
                    }
                    _drawableComponents.Insert(num2, drawable);
                    drawable.DrawOrderChanged += DrawableDrawOrderChanged;
                }
            }
        }

        private void DrawableDrawOrderChanged(object sender, EventArgs e) {
            IDrawable drawable = sender as IDrawable;
            _drawableComponents.Remove(drawable);
            int num = _drawableComponents.BinarySearch(drawable, DrawOrderComparer.Default);
            if (num < 0) {
                num = ~num;
                while (num < _drawableComponents.Count && _drawableComponents[num].DrawOrder == drawable.DrawOrder) {
                    num++;
                }
                _drawableComponents.Insert(num, drawable);
            }
        }
        private void UpdateableUpdateOrderChanged(object sender, EventArgs e) {
            IUpdateable updateable = sender as IUpdateable;
            _updateableComponents.Remove(updateable);
            int num = _updateableComponents.BinarySearch(updateable, UpdateOrderComparer.Default);
            if (num < 0) {
                num = ~num;
                while (num < _updateableComponents.Count && _updateableComponents[num].UpdateOrder == updateable.UpdateOrder) {
                    num++;
                }
                _updateableComponents.Insert(num, updateable);
            }
        }

    #endregion
    }

    internal class DrawOrderComparer : IComparer<IDrawable> {
        public static readonly DrawOrderComparer Default = new DrawOrderComparer();
        public int Compare(IDrawable x, IDrawable y) {
            if (x == null && y == null) {
                return 0;
            }
            if (x == null) {
                return 1;
            }
            if (y == null) {
                return -1;
            }
            if (x.Equals(y)) {
                return 0;
            }
            if (x.DrawOrder < y.DrawOrder) {
                return -1;
            }
            return 1;
        }
    }

    internal class UpdateOrderComparer : IComparer<IUpdateable> {
        public static readonly UpdateOrderComparer Default = new UpdateOrderComparer();
        public int Compare(IUpdateable x, IUpdateable y) {
            if (x == null && y == null) {
                return 0;
            }
            if (x == null) {
                return 1;
            }
            if (y == null) {
                return -1;
            }
            if (x.Equals(y)) {
                return 0;
            }
            if (x.UpdateOrder < y.UpdateOrder) {
                return -1;
            }
            return 1;
        }
    }
}
